package com.flow.framework.core.system.initialization;

import com.flow.framework.common.error.SystemErrorCode;
import com.flow.framework.common.exception.CheckedException;
import com.flow.framework.common.type.TypeReference;
import com.flow.framework.common.util.clazz.ClazzUtil;
import com.flow.framework.common.util.verify.VerifyUtil;
import com.flow.framework.core.system.proxy.IProxy;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 业务代理管理器
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2022/1/23
 */
@Slf4j
public class ProxyManager {

    private static final Map<Type, ProxyService<?, ? extends IProxy>> PROXIED_CLAZZ_TYPE_AND_PROXY_WORKER_MAP = new ConcurrentHashMap<>();

    private static final Object LOCK = new Object();

    private volatile static boolean initialized = false;

    /**
     * 加载被代理的bean
     */
    static void loadProxyBeans() {
        synchronized (LOCK) {
            if (VerifyUtil.isEmpty(PROXIED_CLAZZ_TYPE_AND_PROXY_WORKER_MAP)) {
                return;
            }
            PROXIED_CLAZZ_TYPE_AND_PROXY_WORKER_MAP.forEach((type, proxyService) -> {
                if (type instanceof Class) {
                    Class clazz = (Class) type;
                    String name = clazz.getName();
                    if (name.contains(IProxy.class.getName())) {
                        log.error("proxied beans can't IProxy, clazz name: {}", ClazzUtil.getShortByClazzName(name));
                        throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR);
                    }

                    @SuppressWarnings("unchecked")
                    Collection<? extends IProxy> beans = ApplicationContextHelper.getBeans(clazz);
                    if (VerifyUtil.isEmpty(beans)) {
                        log.error("proxied beans is empty, clazz name: {}", ClazzUtil.getShortByClazzName(name));
                        throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR);
                    }
                    Map<Object, ? extends IProxy> typeAndBeanMap = filterAndClassifyBeans(beans);
                    proxyService.setCache(typeAndBeanMap);
                    return;
                }
                if (type.getTypeName().contains(IProxy.class.getName())) {
                    log.error("proxied beans can't IProxy, clazz name: {}", ClazzUtil.getShortByClazzName(type.getTypeName()));
                    throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR);
                }
                Collection<? extends IProxy> beans = ApplicationContextHelper.getBeansByType(type);
                if (VerifyUtil.isEmpty(beans)) {
                    log.error("proxied beans is empty, clazz name: {}", ClazzUtil.getShortByClazzName(type.getTypeName()));
                    throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR);
                }
                Map<Object, ? extends IProxy> typeAndBeanMap = filterAndClassifyBeans(beans);
                proxyService.setCache(typeAndBeanMap);
            });
            initialized = true;
        }
    }

    /**
     * 获取代理服务
     * 注意：虽然Class实现了Type接口，但是spring在根据Class获取bean和根据Type获取bean还是有区别的，如果根据Class获取
     * 则只要实现了该接口的都会返回，但是如果是根据Type，如果实现类中包含泛型参数，则不会返回包含泛型参数的bean，因为
     * 泛型的Type为ParameterizedType，而Class类并没有实现ParameterizedType
     *
     * @param typeReference 被代理的类型
     * @param <T>           T
     * @param <S>           S
     * @return 代理服务
     */
    public static <T, S extends IProxy<T>> ProxyService<T, S> proxy(TypeReference<S> typeReference) {
        if (null == typeReference) {
            log.error("proxied beans type reference can't be null");
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
        }
        Type type = typeReference.getType();
        if (type.getTypeName().contains(IProxy.class.getName())) {
            log.error("proxied beans can't IProxy, clazz name: {}", ClazzUtil.getShortByClazzName(type.getTypeName()));
            throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR);
        }
        return getProxyService(type);
    }

    /**
     * 获取代理服务
     * 注意：虽然Class实现了Type接口，但是spring在根据Class获取bean和根据Type获取bean还是有区别的，如果根据Class获取
     * 则只要实现了该接口的都会返回，但是如果是根据Type，如果实现类中包含泛型参数，则不会返回包含泛型参数的bean，因为
     * 泛型的Type为ParameterizedType，而Class类并没有实现ParameterizedType
     *
     * @param clazz 被代理的类
     * @param <T>   T
     * @param <S>   S
     * @return 代理服务
     */

    public static <T, S extends IProxy<T>> ProxyService<T, S> proxy(Class<S> clazz) {
        if (null == clazz) {
            log.error("proxied beans clazz can't be null");
            throw new CheckedException(SystemErrorCode.PARAMS_ERROR);
        }
        if (clazz.getName().contains(IProxy.class.getName())) {
            log.error("proxied beans can't IProxy, clazz name: {}", ClazzUtil.getShortByClazzName(clazz.getName()));
            throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR);
        }
        return getProxyService(clazz);
    }

    @SuppressWarnings("unchecked")
    private static <T, S extends IProxy<T>> ProxyService<T, S> getProxyService(Type type) {
        ProxyService<?, ? extends IProxy> proxyService = PROXIED_CLAZZ_TYPE_AND_PROXY_WORKER_MAP.get(type);
        String typeName = type.getTypeName();
        if (null == proxyService) {
            synchronized (LOCK) {
                proxyService = PROXIED_CLAZZ_TYPE_AND_PROXY_WORKER_MAP.get(type);
                if (null == proxyService) {
                    ProxyService<T, S> cache = new ProxyService<T, S>(type, null);
                    if (initialized) {
                        Collection<S> beans;
                        if (type instanceof Class) {
                            Class<S> clazz = (Class<S>) type;
                            beans = ApplicationContextHelper.getBeans(clazz);
                        } else {
                            beans = ApplicationContextHelper.getBeansByType(type);
                        }
                        if (VerifyUtil.isEmpty(beans)) {
                            log.error("proxied beans is empty, clazz name: {}", ClazzUtil.getShortByClazzName(typeName));
                            throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR);
                        }
                        Map<T, S> typeAndBeanMap = (Map<T, S>) filterAndClassifyBeans(beans);
                        cache.setCache(typeAndBeanMap);
                    }
                    PROXIED_CLAZZ_TYPE_AND_PROXY_WORKER_MAP.put(type, cache);
                    return cache;
                }
                return (ProxyService<T, S>) proxyService;
            }
        }
        return (ProxyService<T, S>) proxyService;
    }

    private static Map<Object, ? extends IProxy> filterAndClassifyBeans(Collection<? extends IProxy> beans) {
        Map<Object, IProxy> typeAndBeanMap = new HashMap<>(16);
        for (IProxy bean : beans) {
            Object[] types = bean.getTypes();
            if (VerifyUtil.isEmpty(types)) {
                log.error("proxied bean type is empty, clazz name: {}", ClazzUtil.getShortByClazzName(bean.getClass().getName()));
                throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR);
            }
            for (Object type : types) {
                if (typeAndBeanMap.containsKey(type)) {
                    log.error("proxied bean type already exist, clazz name: {}, type : {}", ClazzUtil.getShortByClazzName(bean.getClass().getName()),
                            ClazzUtil.getShortByClazzName(type.getClass().getName()));
                    throw new CheckedException(SystemErrorCode.SYSTEM_START_ERROR);
                }
                typeAndBeanMap.put(type, bean);
            }
        }
        return typeAndBeanMap;
    }
}